summaryrefslogtreecommitdiff
path: root/app/[lng]
diff options
context:
space:
mode:
Diffstat (limited to 'app/[lng]')
-rw-r--r--app/[lng]/evcp/(evcp)/(master-data)/buyer-signature/page.tsx3
-rw-r--r--app/[lng]/evcp/(evcp)/(procurement)/pq_new/[vendorId]/[submissionId]/page.tsx57
-rw-r--r--app/[lng]/partners/(partners)/pq_new/[id]/page.tsx63
-rw-r--r--app/[lng]/partners/(partners)/pq_new/page.tsx49
4 files changed, 161 insertions, 11 deletions
diff --git a/app/[lng]/evcp/(evcp)/(master-data)/buyer-signature/page.tsx b/app/[lng]/evcp/(evcp)/(master-data)/buyer-signature/page.tsx
index dfbd605b..5826b8d8 100644
--- a/app/[lng]/evcp/(evcp)/(master-data)/buyer-signature/page.tsx
+++ b/app/[lng]/evcp/(evcp)/(master-data)/buyer-signature/page.tsx
@@ -8,6 +8,7 @@ export default async function BuyerSignaturePage(props: { params: Promise<{ lng:
const { t } = await useTranslation(lng, 'menu')
const signatures = await getAllSignatures();
+ const activeSignature = signatures.find((signature) => signature.isActive);
return (
<div className="container mx-auto py-8 max-w-4xl">
@@ -19,7 +20,7 @@ export default async function BuyerSignaturePage(props: { params: Promise<{ lng:
</p>
</div>
- <BuyerSignatureUploadForm />
+ <BuyerSignatureUploadForm initialSignature={activeSignature} />
<SignatureList signatures={signatures} />
</div>
diff --git a/app/[lng]/evcp/(evcp)/(procurement)/pq_new/[vendorId]/[submissionId]/page.tsx b/app/[lng]/evcp/(evcp)/(procurement)/pq_new/[vendorId]/[submissionId]/page.tsx
index 8e3006cc..5baf6efb 100644
--- a/app/[lng]/evcp/(evcp)/(procurement)/pq_new/[vendorId]/[submissionId]/page.tsx
+++ b/app/[lng]/evcp/(evcp)/(procurement)/pq_new/[vendorId]/[submissionId]/page.tsx
@@ -54,7 +54,11 @@ export default async function PQReviewPage(props: PQReviewPageProps) {
const pqSubmission = await getPQById(submissionId, vendorId)
// PQ 데이터 조회 (질문과 답변)
- const pqData = await getPQDataByVendorId(vendorId, pqSubmission.projectId || undefined)
+ const pqData = await getPQDataByVendorId(
+ vendorId,
+ pqSubmission.projectId || undefined,
+ pqSubmission.type as "GENERAL" | "PROJECT" | "NON_INSPECTION"
+ )
// 협력업체 정보 (pqSubmission에 이미 포함되어 있음)
const vendorInfo = {
@@ -64,7 +68,6 @@ export default async function PQReviewPage(props: PQReviewPageProps) {
vendorCountry: pqSubmission.vendorCountry,
vendorEmail: pqSubmission.vendorEmail,
vendorPhone: pqSubmission.vendorPhone,
- vendorFax: pqSubmission.vendorFax,
}
// 프로젝트 정보 (프로젝트 PQ인 경우)
@@ -114,7 +117,30 @@ export default async function PQReviewPage(props: PQReviewPageProps) {
<Alert>
<AlertTitle>제출 완료</AlertTitle>
<AlertDescription>
- 협력업체가 {formatDate(pqSubmission.submittedAt, "kr")}에 PQ를 제출했습니다. 검토 후 승인 또는 거부할 수 있습니다.
+ 협력업체가 {pqSubmission.submittedAt ? formatDate(pqSubmission.submittedAt) : "N/A"}에 PQ를 제출했습니다. 검토 후 승인 또는 거부할 수 있습니다.
+ </AlertDescription>
+ </Alert>
+ )}
+
+ {pqSubmission.status === "SAFETY_APPROVED" && (
+ <Alert variant="success">
+ <AlertTitle>안전 검토 승인됨</AlertTitle>
+ <AlertDescription>
+ 안전팀 검토가 완료되었습니다. 구매 승인 단계를 진행해주세요.
+ </AlertDescription>
+ </Alert>
+ )}
+
+ {pqSubmission.status === "SAFETY_REJECTED" && (
+ <Alert variant="destructive">
+ <AlertTitle>안전 검토 거절됨</AlertTitle>
+ <AlertDescription>
+ 안전팀에서 PQ를 거절했습니다.
+ {pqSubmission.rejectReason && (
+ <div className="mt-2">
+ <strong>사유:</strong> {pqSubmission.rejectReason}
+ </div>
+ )}
</AlertDescription>
</Alert>
)}
@@ -123,7 +149,7 @@ export default async function PQReviewPage(props: PQReviewPageProps) {
<Alert variant="success">
<AlertTitle>승인됨</AlertTitle>
<AlertDescription>
- {formatDate(pqSubmission.approvedAt, "kr")}에 승인되었습니다.
+ {pqSubmission.approvedAt ? formatDate(pqSubmission.approvedAt) : "N/A"}에 승인되었습니다.
</AlertDescription>
</Alert>
)}
@@ -132,7 +158,7 @@ export default async function PQReviewPage(props: PQReviewPageProps) {
<Alert variant="destructive">
<AlertTitle>거부됨</AlertTitle>
<AlertDescription>
- {formatDate(pqSubmission.rejectedAt, "kr")}에 거부되었습니다.
+ {pqSubmission.rejectedAt ? formatDate(pqSubmission.rejectedAt) : "N/A"}에 거부되었습니다.
{pqSubmission.rejectReason && (
<div className="mt-2">
<strong>사유:</strong> {pqSubmission.rejectReason}
@@ -157,6 +183,7 @@ export default async function PQReviewPage(props: PQReviewPageProps) {
vendorId={vendorId}
pqSubmission={pqSubmission}
vendorInfo={vendorInfo}
+ vendorCountry={pqSubmission.vendorCountry}
/>
</TabsContent>
@@ -212,10 +239,20 @@ function getStatusLabel(status: string): string {
return "진행 중";
case "SUBMITTED":
return "제출됨";
+ case "SAFETY_APPROVED":
+ return "안전 승인됨";
+ case "SAFETY_REJECTED":
+ return "안전 거절됨";
case "APPROVED":
return "승인됨";
case "REJECTED":
return "거부됨";
+ case "QM_REVIEWING":
+ return "QM 검토중";
+ case "QM_APPROVED":
+ return "QM 승인됨";
+ case "QM_REJECTED":
+ return "QM 거절됨";
default:
return status;
}
@@ -230,10 +267,20 @@ function getStatusVariant(status: string): "default" | "outline" | "secondary" |
return "secondary";
case "SUBMITTED":
return "default";
+ case "SAFETY_APPROVED":
+ return "secondary";
+ case "SAFETY_REJECTED":
+ return "destructive";
case "APPROVED":
return "success";
case "REJECTED":
return "destructive";
+ case "QM_REVIEWING":
+ return "secondary";
+ case "QM_APPROVED":
+ return "default";
+ case "QM_REJECTED":
+ return "destructive";
default:
return "outline";
}
diff --git a/app/[lng]/partners/(partners)/pq_new/[id]/page.tsx b/app/[lng]/partners/(partners)/pq_new/[id]/page.tsx
index f298cc77..448267d0 100644
--- a/app/[lng]/partners/(partners)/pq_new/[id]/page.tsx
+++ b/app/[lng]/partners/(partners)/pq_new/[id]/page.tsx
@@ -3,6 +3,7 @@ import Link from "next/link";
import { getServerSession } from "next-auth/next";
import { authOptions } from "@/app/api/auth/[...nextauth]/route";
import { Button } from "@/components/ui/button";
+import { Badge } from "@/components/ui/badge";
import { ArrowLeft, LogIn } from "lucide-react";
import { Shell } from "@/components/shell";
import { getPQById, getPQDataByVendorId } from "@/lib/pq/service";
@@ -29,6 +30,32 @@ export default async function PQEditPage(props: PQEditPageProps) {
const params = await props.params;
const pqSubmissionId = parseInt(params.id, 10);
+ if (Number.isNaN(pqSubmissionId)) {
+ return (
+ <Shell className="gap-6">
+ <div className="flex items-center justify-between">
+ <div>
+ <h2 className="text-2xl font-bold tracking-tight">잘못된 PQ ID</h2>
+ <p className="text-muted-foreground">유효한 PQ ID가 아닙니다.</p>
+ </div>
+ </div>
+ <div className="flex flex-col items-center justify-center py-12 text-center">
+ <div className="rounded-lg border border-dashed p-10 shadow-sm">
+ <p className="mb-6 text-muted-foreground">
+ 요청하신 PQ ID를 확인하고 다시 시도해주세요.
+ </p>
+ <Button asChild>
+ <Link href="/partners/pq_new">
+ <ArrowLeft className="mr-2 h-4 w-4" />
+ 목록으로 돌아가기
+ </Link>
+ </Button>
+ </div>
+ </div>
+ </Shell>
+ );
+ }
+
// 인증 확인
const session = await getServerSession(authOptions);
@@ -100,12 +127,17 @@ export default async function PQEditPage(props: PQEditPageProps) {
}
// PQ 데이터 조회 (pqCriterias와 답변)
- const pqData = await getPQDataByVendorId(idAsNumber, pqSubmission.projectId || undefined);
+ const pqData = await getPQDataByVendorId(
+ idAsNumber,
+ pqSubmission.projectId || undefined,
+ pqSubmission.type as "GENERAL" | "PROJECT" | "NON_INSPECTION"
+ );
// 상태에 따른 읽기 전용 모드 결정
- const isReadOnly = [ "APPROVED"].includes(pqSubmission.status);
+ const isReadOnly = [ "QM_APPROVED"].includes(pqSubmission.status);
const statusText = pqSubmission.status === "SUBMITTED" ? "제출됨" :
pqSubmission.status === "APPROVED" ? "승인됨" :
+ pqSubmission.status === "QM_APPROVED" ? "최종 승인됨" :
pqSubmission.status === "REJECTED" ? "거부됨" : "작성 중";
const pageTitle = pqSubmission.type === "PROJECT"
@@ -159,6 +191,32 @@ export default async function PQEditPage(props: PQEditPageProps) {
</Alert>
)} */}
+ <div className="rounded-lg border p-4 space-y-2">
+ <h3 className="text-sm font-semibold">PQ 품목</h3>
+ {Array.isArray(pqSubmission.pqItems) && pqSubmission.pqItems.length > 0 ? (
+ <div className="flex flex-wrap gap-2">
+ {pqSubmission.pqItems.map((item: any, idx: number) => {
+ // materialGroupCode/materialGroupDescription 형식 지원 (새로운 형식)
+ const displayName = item?.materialGroupDescription || item?.itemName || "";
+ const displayCode = item?.materialGroupCode || item?.itemCode || "";
+
+ if (!displayName && !displayCode) return null;
+
+ return (
+ <Badge key={idx} variant="outline">
+ {displayName || displayCode || "품목"}
+ {displayCode && displayName !== displayCode ? ` (${displayCode})` : ""}
+ </Badge>
+ );
+ })}
+ </div>
+ ) : pqSubmission.pqItems ? (
+ <div className="text-sm">{String(pqSubmission.pqItems)}</div>
+ ) : (
+ <p className="text-sm text-muted-foreground">지정된 PQ 품목이 없습니다.</p>
+ )}
+ </div>
+
{/* PQ 입력 컴포넌트 */}
<PQInputTabs
data={pqData}
@@ -171,6 +229,7 @@ export default async function PQEditPage(props: PQEditPageProps) {
status: pqSubmission.status,
type: pqSubmission.type
}}
+ vendorCountry={pqSubmission.vendorCountry}
/>
</Shell>
);
diff --git a/app/[lng]/partners/(partners)/pq_new/page.tsx b/app/[lng]/partners/(partners)/pq_new/page.tsx
index fb77ce0e..89a646a8 100644
--- a/app/[lng]/partners/(partners)/pq_new/page.tsx
+++ b/app/[lng]/partners/(partners)/pq_new/page.tsx
@@ -51,6 +51,12 @@ function getStatusBadge(status: string) {
return <Badge variant="default">승인됨</Badge>;
case "REJECTED":
return <Badge variant="destructive">거부됨</Badge>;
+ case "QM_REVIEWING":
+ return <Badge variant="secondary">QM 검토 중</Badge>;
+ case "QM_APPROVED":
+ return <Badge variant="default">최종 승인됨</Badge>;
+ case "QM_REJECTED":
+ return <Badge variant="destructive">최종 거부됨</Badge>;
default:
return <Badge variant="outline">{status}</Badge>;
}
@@ -65,6 +71,39 @@ function getFormattedDate(date: Date | null) {
}).format(new Date(date));
}
+function renderPQItems(pqItems: unknown) {
+ if (!pqItems) return "-";
+
+ if (typeof pqItems === "string") {
+ return pqItems || "-";
+ }
+
+ if (Array.isArray(pqItems)) {
+ if (pqItems.length === 0) return "-";
+
+ return (
+ <div className="flex flex-wrap gap-1">
+ {pqItems.map((item: any, idx: number) => {
+ // materialGroupCode/materialGroupDescription 형식 지원 (새로운 형식)
+ const displayName = item?.materialGroupDescription || item?.itemName || "";
+ const displayCode = item?.materialGroupCode || item?.itemCode || "";
+
+ if (!displayName && !displayCode) return null;
+
+ return (
+ <Badge key={idx} variant="outline">
+ {displayName || displayCode || "품목"}
+ {displayCode && displayName !== displayCode ? ` (${displayCode})` : ""}
+ </Badge>
+ );
+ })}
+ </div>
+ );
+ }
+
+ return "-";
+}
+
export default async function PQListPage({ params }: IndexPageProps) {
// 캐시 비활성화
noStore();
@@ -212,6 +251,7 @@ export default async function PQListPage({ params }: IndexPageProps) {
<TableHead>유형</TableHead>
<TableHead>PQ 번호</TableHead>
<TableHead>프로젝트</TableHead>
+ <TableHead>PQ 품목</TableHead>
<TableHead>상태</TableHead>
<TableHead>요청일</TableHead>
<TableHead>제출일</TableHead>
@@ -222,14 +262,14 @@ export default async function PQListPage({ params }: IndexPageProps) {
<TableBody>
{pqList.length === 0 ? (
<TableRow>
- <TableCell colSpan={8} className="text-center py-8 text-muted-foreground">
+ <TableCell colSpan={9} className="text-center py-8 text-muted-foreground">
요청된 PQ가 없습니다.
</TableCell>
</TableRow>
) : (
pqList.map((pq) => {
- const canEdit = ["REQUESTED", "IN_PROGRESS", "REJECTED"].includes(pq.status);
- const canView = ["SUBMITTED", "APPROVED"].includes(pq.status);
+ const canEdit = ["REQUESTED", "IN_PROGRESS", "REJECTED", "SAFETY_APPROVED", "QM_REVIEWING"].includes(pq.status);
+ const canView = ["SUBMITTED", "APPROVED", "QM_APPROVED", "SAFETY_REJECTED", "QM_REJECTED"].includes(pq.status);
return (
<TableRow key={pq.id}>
@@ -251,6 +291,9 @@ export default async function PQListPage({ params }: IndexPageProps) {
{pq.projectName || "-"}
</TableCell>
<TableCell>
+ {renderPQItems(pq.pqItems)}
+ </TableCell>
+ <TableCell>
{getStatusBadge(pq.status)}
</TableCell>
<TableCell>